using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Media;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Directory;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Logistics;
using Nop.Services.Media;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Seo;
using Nop.Services.Shipping;
using Nop.Services.Tax;
using Nop.Web.Extensions;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Security;
using Nop.Web.Infrastructure.Cache;
using Nop.Web.Models.Common;
using Nop.Web.Models.Media;
using Nop.Web.Models.Order;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web.Mvc;

namespace Nop.Web.Controllers
{
    public partial class OrderController : BaseNopController
    {
        #region Fields

        private readonly IOrderService _orderService;
        private readonly IShipmentService _shipmentService;
        private readonly IWorkContext _workContext;
        private readonly ICurrencyService _currencyService;
        private readonly IPriceFormatter _priceFormatter;
        private readonly Nop.Services.Orders.IOrderProcessingService _orderProcessingService;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly IPaymentService _paymentService;
        private readonly IPictureService _pictureService;
        private readonly ILocalizationService _localizationService;
        private readonly IPdfService _pdfService;
        private readonly IShippingService _shippingService;
        private readonly ICountryService _countryService;
        private readonly ITaxService _taxService;
        private readonly ICacheManager _cacheManager;
        private readonly IWebHelper _webHelper;

        private readonly ILogisticsService _logisticsService;
        private readonly MediaSettings _mediaSettings;
        private readonly OrderSettings _orderSettings;
        private readonly TaxSettings _taxSettings;
        private readonly CatalogSettings _catalogSettings;
        private readonly PdfSettings _pdfSettings;
        private readonly ShippingSettings _shippingSettings;
        private readonly AddressSettings _addressSettings;
        private readonly ILogisticsProcessingService _logisticsProcessingService;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly ICustomerActivityService _customerActivityService;
        private readonly ICheckoutAttributeParser _checkoutAttributeParser;


        #endregion

        #region Constructors

        public OrderController(IOrderService orderService,
            IShipmentService shipmentService, IWorkContext workContext,
            ICurrencyService currencyService, IPriceFormatter priceFormatter,
            Nop.Services.Orders.IOrderProcessingService orderProcessingService, IPictureService pictureService, IDateTimeHelper dateTimeHelper,
            IPaymentService paymentService, ILocalizationService localizationService,
            IPdfService pdfService, IShippingService shippingService,
            ITaxService taxService,
            ICountryService countryService, IWebHelper webHelper, ICacheManager cacheManager,
            MediaSettings mediaSettings, CatalogSettings catalogSettings, OrderSettings orderSettings,
            TaxSettings taxSettings, PdfSettings pdfSettings,
            ShippingSettings shippingSettings, AddressSettings addressSettings, ILogisticsService logisticsService
            , ILogisticsProcessingService logisticsProcessingService, IGenericAttributeService genericAttributeService,
            ICustomerActivityService customerActivityService, ICheckoutAttributeParser checkoutAttributeParser)
        {
            this._orderService = orderService;
            this._shipmentService = shipmentService;
            this._workContext = workContext;
            this._currencyService = currencyService;
            this._priceFormatter = priceFormatter;
            this._orderProcessingService = orderProcessingService;
            this._dateTimeHelper = dateTimeHelper;
            this._paymentService = paymentService;
            this._pictureService = pictureService;
            this._localizationService = localizationService;
            this._pdfService = pdfService;
            this._shippingService = shippingService;
            this._logisticsService = logisticsService;
            this._countryService = countryService;
            this._taxService = taxService;
            this._cacheManager = cacheManager;
            this._webHelper = webHelper;

            this._mediaSettings = mediaSettings;
            this._catalogSettings = catalogSettings;
            this._orderSettings = orderSettings;
            this._taxSettings = taxSettings;
            this._pdfSettings = pdfSettings;
            this._shippingSettings = shippingSettings;
            this._addressSettings = addressSettings;
            this._logisticsProcessingService = logisticsProcessingService;
            this._genericAttributeService = genericAttributeService;
            this._customerActivityService = customerActivityService;
            this._checkoutAttributeParser = checkoutAttributeParser;
        }

        #endregion

        #region Utilities

        [NonAction]
        protected OrderDetailsModel PrepareOrderDetailsModel(Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");
            var model = new OrderDetailsModel();
            var deliveredProducts = new List<int>();
            model.Id = order.Id;
            model.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc);
            model.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext);
            model.IsReOrderAllowed = _orderSettings.IsReOrderAllowed;
            model.DisplayPdfInvoice = _pdfSettings.Enabled;
            model.ShowShipments = false;
            model.OrderKind = order.OrderKind;
            model.PaymentStatus = ((PaymentStatus)order.PaymentStatus).ToString();

            //pickup time
            model.PickUpTimeShow = _logisticsService.GetShowPickUpTime(order.ShipmentDate, order.ShippingTimeWindowsId, order);

            //shipping info
            model.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext);
            if (order.ShippingStatus != ShippingStatus.ShippingNotRequired)
            {
                model.IsShippable = true;
                model.ShippingAddress.PrepareModel(order.ShippingAddress, false, _addressSettings);
                model.ShippingMethod = order.ShippingMethod;
                model.ShowShipments = order.OrderStatus != OrderStatus.Cancelled && order.ShippingStatus != ShippingStatus.Cancelled && order.ShippingStatus != ShippingStatus.CancelledNotRefund && order.ShippingStatus != ShippingStatus.CancelledWithRefund;

                //shipments (only already shipped)
                var shipments = order.Shipments.OrderBy(x => x.CreatedOnUtc).ToList();
                foreach (var shipment in shipments)
                {

                    var shipmentModel = new OrderDetailsModel.ShipmentBriefModel()
                    {
                        Id = shipment.Id,
                        TrackingNumber = shipment.TrackingNumber,
                    };
                    if (shipment.ShippedDateUtc.HasValue)
                        shipmentModel.ShippedDate = _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc);
                    if (shipment.DeliveryDateUtc.HasValue)
                        shipmentModel.DeliveryDate = _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc);

                    // Shipment events
                    var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(order.ShippingRateComputationMethodSystemName);
                    if (srcm != null &&
                        srcm.PluginDescriptor.Installed &&
                        srcm.IsShippingRateComputationMethodActive(_shippingSettings))
                    {
                        var shipmentTracker = srcm.ShipmentTracker;
                        if (shipmentTracker != null)
                        {
                            if (_shippingSettings.DisplayShipmentEventsToCustomers)
                            {
                                var shipmentEvents = shipmentTracker.GetShipmentEvents(shipment.TrackingNumber);
                                if (shipmentEvents != null)
                                {
                                    shipmentModel.ShipmentEvents = new List<ShipmentDetailsModel.ShipmentStatusEventModel>();
                                    foreach (var shipmentEvent in shipmentEvents)
                                    {
                                        // TODO: Handle local time
                                        var culture = new CultureInfo(_workContext.WorkingLanguage.LanguageCulture);
                                        var na = _localizationService.GetResource("Common.NotAvailable");

                                        var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel();
                                        var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode);
                                        shipmentStatusEventModel.Country = shipmentEventCountry != null
                                                                               ? shipmentEventCountry.GetLocalized(x => x.Name)
                                                                               : shipmentEvent.CountryCode;
                                        shipmentStatusEventModel.Date = shipmentEvent.DateTime.HasValue ? shipmentEvent.DateTime.Value.ToString("d", culture) : na;
                                        if (_shippingSettings.DisplayTimeInEvents)
                                            shipmentStatusEventModel.Time = shipmentEvent.DateTime.HasValue ? shipmentEvent.DateTime.Value.ToString("t", culture) : na;

                                        shipmentStatusEventModel.EventName = shipmentEvent.EventName;
                                        shipmentStatusEventModel.Location = shipmentEvent.Location;
                                        shipmentModel.ShipmentEvents.Add(shipmentStatusEventModel);
                                    }
                                }
                            }
                        }
                    }
                    if (shipment.PackageId.HasValue)
                    {
                        var package = _logisticsService.GetPackageById(shipment.PackageId.Value);
                        switch (package.Status)
                        {
                            case ShippingStatus.Cancelled:
                            case ShippingStatus.CancelledNotRefund:
                            case ShippingStatus.CancelledWithRefund:
                                shipmentModel.DisplayedStatus = "Cancelled";
                                break;

                            case ShippingStatus.Delivered:
                                shipmentModel.DisplayedStatus = "Delivered";
                                break;

                            case ShippingStatus.Delivering:
                            case ShippingStatus.InWarehouse:
                            case ShippingStatus.ReturningWarehouse:
                            case ShippingStatus.Shipped:
                                shipmentModel.DisplayedStatus = "In Transit";
                                break;

                            case ShippingStatus.FailedPickUp:
                            case ShippingStatus.InWarehouseFailedDelivery:
                            case ShippingStatus.ReturningWarehouseFailedDelivery:
                                shipmentModel.DisplayedStatus = "Pending";
                                break;

                            case ShippingStatus.NotYetReady:
                            case ShippingStatus.NotYetShipped:
                            case ShippingStatus.PartiallyShipped:
                            case ShippingStatus.ReadyForPickUp:
                                shipmentModel.DisplayedStatus = "Preparing for Shipment";
                                break;
                        }
                        // Determine if products are delivered
                        if (package.Status == ShippingStatus.Delivered)
                        {
                            foreach (var sopv in shipment.ShipmentOrderProductVariants)
                            {
                                var opv = _orderService.GetOrderProductVariantById(sopv.OrderProductVariantId);
                                if (opv != null)
                                {
                                    deliveredProducts.Add(opv.ProductVariant.ProductId);
                                }
                            }
                        }
                    }
                    model.Shipments.Add(shipmentModel);


                }
                deliveredProducts = deliveredProducts.Distinct().ToList();
            }
            model.CanCancel = _orderProcessingService.CanCancelOrder(order);
            model.CanReturn = _orderProcessingService.IsReturnRequestAllowed(order);
            //billing info
            model.BillingAddress.PrepareModel(order.BillingAddress, false, _addressSettings);

            //VAT number
            model.VatNumber = order.VatNumber;

            //payment method
            var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName);
            model.PaymentMethod = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, _workContext.WorkingLanguage.Id) : order.PaymentMethodSystemName;
            model.CanRePostProcessPayment = _paymentService.CanRePostProcessPayment(order);

            //purchase order number (we have to find a better to inject this information because it's related to a certain plugin)
            if (paymentMethod != null && paymentMethod.PluginDescriptor.SystemName.Equals("Payments.PurchaseOrder", StringComparison.InvariantCultureIgnoreCase))
            {
                model.DisplayPurchaseOrderNumber = true;
                model.PurchaseOrderNumber = order.PurchaseOrderNumber;
            }


            //totals
            switch (order.CustomerTaxDisplayType)
            {
                case TaxDisplayType.ExcludingTax:
                    {
                        //order subtotal
                        var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate);
                        model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);
                        //discount (applied to order subtotal)
                        var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate);
                        if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero)
                            model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);
                        //order shipping
                        var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate);
                        model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);
                        //payment method additional fee
                        var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate);
                        if (paymentMethodAdditionalFeeExclTaxInCustomerCurrency > decimal.Zero)
                            model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);
                    }
                    break;
                case TaxDisplayType.IncludingTax:
                    {
                        //order subtotal
                        var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate);
                        model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);
                        //discount (applied to order subtotal)
                        var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate);
                        if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero)
                            model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);
                        //order shipping
                        var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate);
                        model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);
                        //payment method additional fee
                        var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate);
                        if (paymentMethodAdditionalFeeInclTaxInCustomerCurrency > decimal.Zero)
                            model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);
                    }
                    break;
            }

            //tax
            bool displayTax = true;
            bool displayTaxRates = true;
            if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax)
            {
                displayTax = false;
                displayTaxRates = false;
            }
            else
            {
                if (order.OrderTax == 0 && _taxSettings.HideZeroTax)
                {
                    displayTax = false;
                    displayTaxRates = false;
                }
                else
                {
                    var taxRates = _taxService.GetSurchargesByOrder(order).ToList().ToTaxes().ToDistinctTypes();
                    displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Count > 0;
                    displayTax = !displayTaxRates;

                    var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate);
                    //TODO pass languageId to _priceFormatter.FormatPrice
                    model.Tax = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage);

                    foreach (var tr in taxRates)
                    {
                        model.TaxRates.Add(new OrderDetailsModel.TaxRate()
                        {
                            Rate = _priceFormatter.FormatTaxRate(tr),
                            //TODO pass languageId to _priceFormatter.FormatPrice
                            Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage),
                        });
                    }
                }
            }
            model.DisplayTaxRates = displayTaxRates;
            model.DisplayTax = displayTax;


            //discount (applied to order total)
            var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate);
            if (orderDiscountInCustomerCurrency > decimal.Zero)
                model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage);


            //gift cards
            foreach (var gcuh in order.GiftCardUsageHistory)
            {
                model.GiftCards.Add(new OrderDetailsModel.GiftCard()
                {
                    CouponCode = gcuh.GiftCard.GiftCardCouponCode,
                    Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage),
                });
            }

            //reward points           
            if (order.RedeemedRewardPointsEntry != null)
            {
                model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points;
                model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage);
            }

            //total
            var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate);
            model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage);

            //checkout attributes
            model.CheckoutAttributeInfo = order.CheckoutAttributeDescription;

            //order notes
            foreach (var orderNote in order.OrderNotes
                .Where(on => on.DisplayToCustomer)
                .OrderByDescending(on => on.CreatedOnUtc)
                .ToList())
            {
                model.OrderNotes.Add(new OrderDetailsModel.OrderNote()
                {
                    Note = orderNote.FormatOrderNoteText(),
                    CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc)
                });
            }

            //gift message and checkoutattribute
            model.CheckoutAttributeViewModel = _checkoutAttributeParser.ParseCheckoutAttribute(order.CheckoutAttributesXml);

            if (order.OrderKind == OrderKind.Commerce)
            {
                //purchased products
                model.ShowSku = _catalogSettings.ShowProductSku;
                var orderProductVariants = _orderService.GetAllOrderProductVariants(order.Id, null, null, null, null, null, null);
                foreach (var opv in orderProductVariants)
                {
                    /*var picture = opv.ProductVariant.Product.GetDefaultProductPicture(_pictureService);
                
                    var pictureModel = new PictureModel()
                    {
                        ImageUrl = _pictureService.GetPictureUrl(opv.ProductVariant.PictureId, pictureSize),
                        FullSizeImageUrl = _pictureService.GetPictureUrl(picture),
                        Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), opv.ProductVariant.Product.Name),
                        AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), opv.ProductVariant.Product.Name)
                    };*/
                    var pictureSize = _mediaSettings.CartThumbPictureSize;
                    var pictureCacheKey = string.Format(ModelCacheEventConsumer.CART_PICTURE_MODEL_KEY, opv.ProductVariant.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                    var pictureModel = _cacheManager.Get(pictureCacheKey, () =>
                    {
                        //first try to load product variant piture
                        var picture = _pictureService.GetPictureById(opv.ProductVariant.PictureId);
                        if (picture == null)
                        {
                            //if product variant doesn't have any picture assigned, then load product picture
                            picture = _pictureService.GetPicturesByProductId(opv.ProductVariant.Product.Id, 1).FirstOrDefault();
                        }
                        return new PictureModel()
                        {
                            ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize, showDefaultPicture: true),
                            Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), opv.ProductVariant.Product.Name),
                            AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), opv.ProductVariant.Product.Name),
                        };
                    });


                    var opvModel = new OrderDetailsModel.OrderProductVariantModel()
                    {
                        Id = opv.Id,
                        Sku = opv.ProductVariant.Sku,
                        PictureModel = pictureModel,
                        ProductId = opv.ProductVariant.ProductId,
                        ProductSeName = opv.ProductVariant.Product.GetSeName(),
                        Quantity = opv.Quantity,
                        AttributeInfo = opv.AttributeDescription,
                        CanReview = order.OrderStatus != OrderStatus.Cancelled && order.ShippingStatus != ShippingStatus.Cancelled && order.ShippingStatus != ShippingStatus.CancelledNotRefund && order.ShippingStatus != ShippingStatus.CancelledWithRefund &&
                            (order.ShippingStatus == ShippingStatus.ShippingNotRequired || (deliveredProducts.Count > 0 && deliveredProducts.Contains(opv.ProductVariant.ProductId)))
                    };

                    //product name
                    if (!String.IsNullOrEmpty(opv.ProductVariant.GetLocalized(x => x.Name)))
                        opvModel.ProductName = string.Format("{0} ({1})", opv.ProductVariant.Product.GetLocalized(x => x.Name), opv.ProductVariant.GetLocalized(x => x.Name));
                    else
                        opvModel.ProductName = opv.ProductVariant.Product.GetLocalized(x => x.Name);
                    model.Items.Add(opvModel);

                    //unit price, subtotal
                    switch (order.CustomerTaxDisplayType)
                    {
                        case TaxDisplayType.ExcludingTax:
                            {
                                var opvUnitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.UnitPriceExclTax, order.CurrencyRate);
                                opvModel.UnitPrice = _priceFormatter.FormatPrice(opvUnitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);

                                var opvPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.PriceExclTax, order.CurrencyRate);
                                opvModel.SubTotal = _priceFormatter.FormatPrice(opvPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);
                            }
                            break;
                        case TaxDisplayType.IncludingTax:
                            {
                                var opvUnitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.UnitPriceInclTax, order.CurrencyRate);
                                opvModel.UnitPrice = _priceFormatter.FormatPrice(opvUnitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);

                                var opvPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.PriceInclTax, order.CurrencyRate);
                                opvModel.SubTotal = _priceFormatter.FormatPrice(opvPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);
                            }
                            break;
                    }
                }
            }

            return model;
        }

        [NonAction]
        protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment)
        {
            if (shipment == null)
                throw new ArgumentNullException("shipment");

            var order = shipment.Order;
            if (order == null)
                throw new Exception("order cannot be loaded");
            var model = new ShipmentDetailsModel();

            model.Id = shipment.Id;
            if (shipment.ShippedDateUtc.HasValue)
                model.ShippedDate = _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc);
            if (shipment.DeliveryDateUtc.HasValue)
                model.DeliveryDate = _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc);

            //tracking number and shipment information
            model.TrackingNumber = shipment.TrackingNumber;
            var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(order.ShippingRateComputationMethodSystemName);
            if (srcm != null &&
                srcm.PluginDescriptor.Installed &&
                srcm.IsShippingRateComputationMethodActive(_shippingSettings))
            {
                var shipmentTracker = srcm.ShipmentTracker;
                if (shipmentTracker != null)
                {
                    model.TrackingNumberUrl = shipmentTracker.GetUrl(shipment.TrackingNumber);
                    if (_shippingSettings.DisplayShipmentEventsToCustomers)
                    {
                        var shipmentEvents = shipmentTracker.GetShipmentEvents(shipment.TrackingNumber);
                        if (shipmentEvents != null)
                            foreach (var shipmentEvent in shipmentEvents)
                            {
                                // TODO: Handle local time
                                var culture = new CultureInfo(_workContext.WorkingLanguage.LanguageCulture);
                                var na = _localizationService.GetResource("Common.NotAvailable");

                                var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel();
                                var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode);
                                shipmentStatusEventModel.Country = shipmentEventCountry != null
                                                                       ? shipmentEventCountry.GetLocalized(x => x.Name)
                                                                       : shipmentEvent.CountryCode;
                                shipmentStatusEventModel.Date = shipmentEvent.DateTime.HasValue ? shipmentEvent.DateTime.Value.ToString("d", culture) : na;
                                if (_shippingSettings.DisplayTimeInEvents)
                                    shipmentStatusEventModel.Time = shipmentEvent.DateTime.HasValue ? shipmentEvent.DateTime.Value.ToString("t", culture) : na;

                                shipmentStatusEventModel.EventName = shipmentEvent.EventName;
                                shipmentStatusEventModel.Location = shipmentEvent.Location;
                                model.ShipmentStatusEvents.Add(shipmentStatusEventModel);
                            }
                    }
                }
            }

            if (order.OrderKind == OrderKind.Commerce)
            {
                //products in this shipment
                model.ShowSku = _catalogSettings.ShowProductSku;
                foreach (var sopv in shipment.ShipmentOrderProductVariants)
                {
                    var opv = _orderService.GetOrderProductVariantById(sopv.OrderProductVariantId);
                    if (opv == null)
                        continue;

                    var sopvModel = new ShipmentDetailsModel.ShipmentOrderProductVariantModel()
                    {
                        Id = sopv.Id,
                        Sku = opv.ProductVariant.Sku,
                        ProductId = opv.ProductVariant.ProductId,
                        ProductSeName = opv.ProductVariant.Product.GetSeName(),
                        AttributeInfo = opv.AttributeDescription,
                        QuantityOrdered = opv.Quantity,
                        QuantityShipped = sopv.Quantity,
                    };

                    //product name//product name
                    if (!String.IsNullOrEmpty(opv.ProductVariant.GetLocalized(x => x.Name)))
                        sopvModel.ProductName = string.Format("{0} ({1})", opv.ProductVariant.Product.GetLocalized(x => x.Name), opv.ProductVariant.GetLocalized(x => x.Name));
                    else
                        sopvModel.ProductName = opv.ProductVariant.Product.GetLocalized(x => x.Name);
                    model.Items.Add(sopvModel);
                }
            }

            //order details model
            model.Order = PrepareOrderDetailsModel(order);

            if (_shippingSettings.DisplayCountryInEvents && !model.ShipmentStatusEvents.Any(x => String.IsNullOrEmpty(x.Country)))
            {
                model.ShowShipmentCountry = true;
            }
            if (_shippingSettings.DisplayLocationInEvents && !model.ShipmentStatusEvents.Any(x => String.IsNullOrEmpty(x.Location)))
            {
                model.ShowShipmentLocation = true;
            }
            if (_shippingSettings.DisplayTimeInEvents)
            {
                model.ShowShipmentTime = true;
            }

            if (order.OrderKind == OrderKind.Express)
            {
                var package = _logisticsService.GetPackageByShipmentId(shipment.Id);
                if (package != null)
                {
                    model.PickupAddress = new AddressModel();
                    model.PickupAddress.PrepareModel(package.PickUpLocation.Address, false, _addressSettings);

                    model.DeliveryAddress = new AddressModel();
                    model.DeliveryAddress.PrepareModel(package.DeliveryLocation.Address, false, _addressSettings);
                }
            }

            return model;
        }

        #endregion

        #region Order details

        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult Details(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            var model = PrepareOrderDetailsModel(order);

            return View(model);
        }

        [HttpPost]
        public ActionResult OrderDetails(int OrderId)
        {
            var order = _orderService.GetOrderById(OrderId);
            var html = "";
            if (order == null || order.Deleted || order.OrderKind != OrderKind.Commerce || _workContext.CurrentCustomer.Id != order.CustomerId)
                html = "Unable to find order information. The order may be removed, or you don't have the permission to view tis order.";
            else
            {
                var model = PrepareOrderDetailsModel(order);
                html = this.RenderPartialViewToString("AjaxOrderDetails", model);
            }

            return Json(new
            {
                html = html
            });
        }


        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult OrderDetailsSummary(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            var model = PrepareOrderDetailsModel(order);

            return PartialView(model);
        }

        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult PrintOrderDetails(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            var model = PrepareOrderDetailsModel(order);
            model.PrintMode = true;

            return View("Details", model);
        }

        public ActionResult GetPdfInvoice(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            var orders = new List<Order>();
            orders.Add(order);
            byte[] bytes = null;
            using (var stream = new MemoryStream())
            {
                _pdfService.PrintOrdersToPdf(stream, orders, _workContext.WorkingLanguage);
                bytes = stream.ToArray();
            }
            return File(bytes, "application/pdf", string.Format("order_{0}.pdf", order.Id));
        }

        public ActionResult ReOrder(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            _orderProcessingService.ReOrder(order);
            return RedirectToRoute("ShoppingCart");
        }

        [HttpGet]
        public ActionResult Cancel(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            if (_orderProcessingService.CanCancelOrder(order))
            {
                _orderProcessingService.CancelOrder(order, false);
                if (order.PaymentStatus != Core.Domain.Payments.PaymentStatus.Pending
                && order.PaymentStatus != Core.Domain.Payments.PaymentStatus.Refunded)
                    _orderProcessingService.Refund(order);
            }

            var model = PrepareOrderDetailsModel(order);
            return RedirectToAction("Details", new { OrderId = orderId });
        }

        [HttpPost]
        public ActionResult AjaxCancel(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            var message = "";
            var buttons = "<button type='button' class='btn btn-normal' data-dismiss='modal'>Ok</button>";
            bool success = false;
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
            {
                message = "Failed to cancel this order. It is either locked from cancelling, or you do not have the authorization to cancel this order.";
            }
            else if (_orderProcessingService.CanCancelOrder(order))
            {
                _orderProcessingService.CancelOrder(order, false);
                _customerActivityService.InsertActivity("CancelOrder", _localizationService.GetResource("ActivityLog.CancelOrder"), order.Id);
                if (order.PaymentStatus != Core.Domain.Payments.PaymentStatus.Pending
                && order.PaymentStatus != Core.Domain.Payments.PaymentStatus.Refunded)
                    _orderProcessingService.Refund(order);

                message = "Order #" + orderId + " has been cancelled successfully.";
                buttons = "<button type='button' class='btn btn-normal order-cancel-back-button' data-id='" + orderId + "'>Back</button>";
                success = true;
            }
            else
            {
                message = "Failed to cancel this order. It has been blocked from cancelling.";
            }

            return Json(new
            {
                html = message,
                buttons = buttons,
                success = success
            });
        }

        [HttpPost]
        public ActionResult RemovePendingOrder(int OrderId)
        {
            var customer = _workContext.CurrentCustomer;
            var success = false;
            var message = "";
            var order = _orderService.GetOrderById(OrderId);
            if (order == null || order.Deleted || order.OrderKind != OrderKind.Commerce || order.PaymentStatus != Core.Domain.Payments.PaymentStatus.Pending)
                message = "Order not exist or cannot be deleted.";
            else
            {
                try
                {
                    order.PaymentStatus = Core.Domain.Payments.PaymentStatus.Voided;
                    _orderService.UpdateOrder(order);
                    _orderService.DeleteOrder(order);
                    success = true;
                    message = "Successfully deleted pending order #" + OrderId.ToString() + ".";
                }
                catch (Exception e)
                {
                    message = "Unable to delete Order. " + e.Message;
                }
            }
            return Json(new
            {
                message = message,
                success = success
            });

        }

        [HttpPost, ActionName("Details")]
        [FormValueRequired("repost-payment")]
        public ActionResult RePostPayment(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            if (!_paymentService.CanRePostProcessPayment(order))
                return RedirectToRoute("OrderDetails", new { orderId = orderId });

            var postProcessPaymentRequest = new PostProcessPaymentRequest()
            {
                Order = order
            };
            _paymentService.PostProcessPayment(postProcessPaymentRequest);

            if (_webHelper.IsRequestBeingRedirected || _webHelper.IsPostBeingDone)
            {
                //redirection or POST has been done in PostProcessPayment
                return Content("Redirected");
            }
            else
            {
                //if no redirection has been done (to a third-party payment page)
                //theoretically it's not possible
                return RedirectToRoute("OrderDetails", new { orderId = orderId });
            }
        }

        #endregion

        #region Shipment

        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult ShipmentDetails(int shipmentId)
        {
            var shipment = _shipmentService.GetShipmentById(shipmentId);
            if (shipment == null)
                return new HttpUnauthorizedResult();

            var order = shipment.Order;
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            var model = PrepareShipmentDetailsModel(shipment);

            return View(model);
        }

        [HttpPost, NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult AjaxShipmentDetails(int shipmentId)
        {
            try
            {
                var shipment = _shipmentService.GetShipmentById(shipmentId);

                if (shipment == null)
                    return Json(new { error = "shipmentId" });

                var order = shipment.Order;

                if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                    return Json(new { error = "order" });

                var model = PrepareShipmentDetailsModel(shipment);

                var pkg = _logisticsService.GetPackageByShipmentId(shipment.Id);

                model.CanChangeDeliveryAddress = false;
                model.CanChangePickupAddress = false;
                model.CanCancelPakage = false;
                model.ChangeDeliveryAddressIsFree = false;

                if (pkg != null)
                {
                    //bool isFree;
                    var handle = _logisticsProcessingService.CreatePackageHandle(pkg);
                    model.CanChangePickupAddress = handle.CanChangePickupAddress();
                    model.CanChangeDeliveryAddress = handle.CanChangeDeliveryAddress();
                    model.CanCancelPakage = handle.CanCancel();

                    //string url;
                    //if (!isFree)
                    //{
                    //    url = Url.RouteUrl("AddressChange", new { addressId = model.DeliveryAddress.Id, shipmentId = model.Id, addressType = "DeliveryAddress" });
                    //    _genericAttributeService.SaveAttribute<string>(_workContext.CurrentCustomer, "url", url);
                    //}
                    //model.CanChangePickupAddress = _logisticsProcessingService.CanChangePickupAddress(pkg);
                    //model.CanCancelPakage = _logisticsProcessingService.CanCancelPackage(pkg);
                }

                return Json(new
                {
                    html = this.RenderPartialViewToString(model)
                });
            }
            catch (Exception ex)
            {
                return Json(new { error = ex.Message });
            }
        }

        #endregion
    }
}
